﻿
using System;
using UnityEngine;
using System.Collections.Generic;
using UnityEngine.UI;
using UnityEngine.Events;
using UnityEngine.SocialPlatforms;

[RequireComponent(typeof(ScrollRect))]
public class ListView : MonoBehaviour
{

    #region Public API

    public Dictionary<int, ListViewCell> VisibleCells => m_visibleCells;

    public ListViewController dataSource
    {
        get { return m_dataSource; }
        set { m_dataSource = value; m_requiresReload = true; }
    }

    [System.Serializable]
    public class CellVisibilityChangeEvent : UnityEvent<int, bool> { }

    [HideInInspector]
    public CellVisibilityChangeEvent onCellVisibilityChanged;

    public ListViewCell GetReusableCell(string reuseIdentifier)
    {
        LinkedList<ListViewCell> cells;
        if (!m_reusableCells.TryGetValue(reuseIdentifier, out cells))
        {
            return null;
        }
        if (cells.Count == 0)
        {
            return null;
        }
        ListViewCell cell = cells.First.Value;
        cells.RemoveFirst();
        return cell;
    }

    public bool isEmpty { get; private set; }

    public void ReloadData()
    {
        //UnityEngine.Profiling.Profiler.BeginSample("TableView.ReloadData for dataSource:" + m_dataSource.GetType().Name, this);

        if (m_verticalLayoutGroup == null)
        {
            Debug.LogError("Vertical Layout Group is null");
            return;
        }
        m_rowHeights = new float[m_dataSource.GetNumberOfRowsForTableView(this)];
        this.isEmpty = m_rowHeights.Length == 0;
        ClearAllRows();
        if (this.isEmpty)
        {
            return;
        }
        m_cumulativeRowHeights = new float[m_rowHeights.Length];
        m_cleanCumulativeIndex = -1;

        for (int i = 0; i < m_rowHeights.Length; i++)
        {
            var cellSize = m_dataSource.GetHeightForRowInTableView(this, i);
            m_rowHeights[i] = cellSize.y; //TODO这里要改横竖兼容
            if (i > 0)
            {
                m_rowHeights[i] += m_verticalLayoutGroup.spacing;
            }
        }

        m_scrollRect.content.sizeDelta = new Vector2(m_scrollRect.content.sizeDelta[0],
            GetCumulativeRowHeight(m_rowHeights.Length - 1) + m_verticalLayoutGroup.padding.vertical);

        RecalculateVisibleRowsFromScratch();
        m_requiresReload = false;
        //UnityEngine.Profiling.Profiler.EndSample();

        dataSource.OnTableViewLoaded();

    }

    public ListViewCell GetCellAtRow(int row)
    {
        ListViewCell retVal = null;
        m_visibleCells.TryGetValue(row, out retVal);
        return retVal;
    }

    public Range visibleRowRange
    {
        get { return m_visibleRowRange; }
    }

    public void NotifyCellDimensionsChanged(int row)
    {
        float oldHeight = m_rowHeights[row];
        var cellSize = m_dataSource.GetHeightForRowInTableView(this, row); //TODO这里要改横竖兼容
        m_rowHeights[row] = cellSize.y;
        if (row > 0)
        {
            m_rowHeights[row] += m_verticalLayoutGroup.spacing;
        }
        m_cleanCumulativeIndex = Mathf.Min(m_cleanCumulativeIndex, row - 1);
        ListViewCell cell = GetCellAtRow(row);
        if (cell != null)
        {
            cell.GetComponent<LayoutElement>().preferredHeight = m_rowHeights[row];
            if (row > 0)
            {
                cell.GetComponent<LayoutElement>().preferredHeight -= m_verticalLayoutGroup.spacing;
            }
        }
        float heightDelta = m_rowHeights[row] - oldHeight;
        m_scrollRect.content.sizeDelta = new Vector2(m_scrollRect.content.sizeDelta.x,
            m_scrollRect.content.sizeDelta.y + heightDelta);
        m_requiresRefresh = true;
    }

    public float scrollableHeight
    {
        get
        {
            return m_scrollRect.content.rect.height - (this.transform as RectTransform).rect.height;
        }
    }

    public float scrollY
    {
        get
        {
            return m_scrollY;
        }
        set
        {
            if (this.isEmpty)
            {
                return;
            }
            value = Mathf.Clamp(value, 0, GetScrollYForRow(m_rowHeights.Length - 1, true));
            if (m_scrollY != value)
            {
                m_scrollY = value;
                m_requiresRefresh = true;
                float relativeScroll = value / this.scrollableHeight;
                //if (relativeScroll > 1)
                //    relativeScroll = 1;
                m_scrollRect.verticalNormalizedPosition = 1 - relativeScroll;
            }
        }
    }

    public float GetScrollYForRow(int row, bool above)
    {
        float retVal = GetCumulativeRowHeight(row);
        retVal += m_verticalLayoutGroup.padding.top;
        if (above)
        {
            retVal -= m_rowHeights[row];
        }
        return retVal;
    }

    #endregion

    #region Private implementation

    private ListViewController m_dataSource;  // 复用滚动框子项的数据
    private bool m_requiresReload;  // 是否需要刷新ListView

    private VerticalLayoutGroup m_verticalLayoutGroup;

    private ScrollRect m_scrollRect;
    private LayoutElement m_topContentPlaceHolder;
    private LayoutElement m_bottomContentPlaceholder;

    private float[] m_rowHeights;
    //cumulative[i] = sum(rowHeights[j] for 0 <= j <= i)
    private float[] m_cumulativeRowHeights;
    private int m_cleanCumulativeIndex;

    private Dictionary<int, ListViewCell> m_visibleCells;  // 可见范围内的子项
    private Range m_visibleRowRange;

    private RectTransform m_reusableCellContainer;
    private Dictionary<string, LinkedList<ListViewCell>> m_reusableCells;  // 子项对象池

    private float m_scrollY;

    private bool m_requiresRefresh;  // 是否需要刷新列表

    private void ScrollViewValueChanged(Vector2 newScrollValue)
    {
        float relativeScrollY = 1 - newScrollValue.y;
        m_scrollY = relativeScrollY * scrollableHeight;
        m_requiresRefresh = true;

        //Debug.Log(m_scrollY.ToString(("0.00")));
    }

    private void RecalculateVisibleRowsFromScratch()
    {
        ClearAllRows();
        SetInitialVisibleRows();
    }

    private void ClearAllRows()
    {
        while (m_visibleCells.Count > 0)
        {
            HideRow(false);
        }
        m_visibleRowRange = new Range(0, 0);
    }

    void Awake()
    {
        isEmpty = true;
        m_scrollRect = GetComponent<ScrollRect>();
        m_verticalLayoutGroup = GetComponentInChildren<VerticalLayoutGroup>();
        m_topContentPlaceHolder = CreateEmptyContentPlaceHolderElement("TopContentPlaceHolder");
        m_topContentPlaceHolder.transform.SetParent(m_scrollRect.content, false);
        m_bottomContentPlaceholder = CreateEmptyContentPlaceHolderElement("BottomContentPlaceHolder");
        m_bottomContentPlaceholder.transform.SetParent(m_scrollRect.content, false);
        m_visibleCells = new Dictionary<int, ListViewCell>();

        m_reusableCellContainer = new GameObject("ReusableCells", typeof(RectTransform)).GetComponent<RectTransform>();
        m_reusableCellContainer.SetParent(this.transform, false);
        m_reusableCellContainer.gameObject.SetActive(false);
        m_reusableCells = new Dictionary<string, LinkedList<ListViewCell>>();
    }

    void Update()
    {
        if (m_requiresReload)
        {
            ReloadData();
        }
    }

    void LateUpdate()
    {
        if (m_requiresRefresh)
        {
            RefreshVisibleRows();
        }
    }

    void OnEnable()
    {
        m_scrollRect.onValueChanged.AddListener(ScrollViewValueChanged);
    }

    void OnDisable()
    {
        m_scrollRect.onValueChanged.RemoveListener(ScrollViewValueChanged);
    }

    private Range CalculateCurrentVisibleRowRange()
    {

        float startY = Math.Max(m_scrollY - m_verticalLayoutGroup.padding.top, 0);

        var visibleTopPadding = Math.Max(m_verticalLayoutGroup.padding.top - m_scrollY, 0);
        float endY = startY + (this.transform as RectTransform).rect.height - visibleTopPadding;

        int startIndex = FindIndexOfRowAtY(startY);
        int endIndex = FindIndexOfRowAtY(endY);

        return new Range(startIndex, endIndex - startIndex + 1);
    }

    private void SetInitialVisibleRows()
    {
        Range visibleRows = CalculateCurrentVisibleRowRange();
        for (int i = 0; i < visibleRows.count; i++)
        {
            AddRow(visibleRows.from + i, true);
        }
        m_visibleRowRange = visibleRows;
        UpdatePaddingElements();
    }

    private void AddRow(int row, bool atEnd)
    {
        ListViewCell newCell = m_dataSource.GetCellForRowInTableView(this, row);
        //Debug.Log("1111111111   " + newCell.RectTransform.localPosition);
        newCell.transform.SetParent(m_scrollRect.content, false);
        //Debug.Log("22222   " + newCell.RectTransform.localPosition);
        LayoutElement layoutElement = newCell.GetComponent<LayoutElement>();
        if (layoutElement == null)
        {
            layoutElement = newCell.gameObject.AddComponent<LayoutElement>();
        }
        layoutElement.preferredHeight = m_rowHeights[row];
        if (row > 0)
        {
            layoutElement.preferredHeight -= m_verticalLayoutGroup.spacing;
        }

        m_visibleCells[row] = newCell;
        if (atEnd)
        {
            newCell.transform.SetSiblingIndex(m_scrollRect.content.childCount - 2); //One before bottom padding
        }
        else
        {
            newCell.transform.SetSiblingIndex(1); //One after the top padding
        }
        this.onCellVisibilityChanged.Invoke(row, true);
        m_dataSource.OnUpdateCell(newCell);
    }

    private void RefreshVisibleRows()
    {
        m_requiresRefresh = false;

        if (this.isEmpty)
        {
            return;
        }

        Range newVisibleRows = CalculateCurrentVisibleRowRange();
        int oldTo = m_visibleRowRange.Last();
        int newTo = newVisibleRows.Last();

        if (newVisibleRows.from > oldTo || newTo < m_visibleRowRange.from)
        {
            RecalculateVisibleRowsFromScratch();
            return;
        }

        for (int i = m_visibleRowRange.from; i < newVisibleRows.from; i++)
        {
            HideRow(false);
        }
        for (int i = newTo; i < oldTo; i++)
        {
            HideRow(true);
        }
        for (int i = m_visibleRowRange.from - 1; i >= newVisibleRows.from; i--)
        {
            AddRow(i, false);
        }
        for (int i = oldTo + 1; i <= newTo; i++)
        {
            AddRow(i, true);
        }
        m_visibleRowRange = newVisibleRows;
        UpdatePaddingElements();
    }

    private void UpdatePaddingElements()
    {
        float hiddenElementsHeightSum = 0;

        for (int i = 0; i < m_visibleRowRange.from; i++)
        {
            hiddenElementsHeightSum += m_rowHeights[i];
        }
        var topContentPlaceHolderHeight = hiddenElementsHeightSum;
        m_topContentPlaceHolder.preferredHeight = topContentPlaceHolderHeight;
        m_topContentPlaceHolder.gameObject.SetActive(m_topContentPlaceHolder.preferredHeight > 0);
        for (int i = m_visibleRowRange.from; i <= m_visibleRowRange.Last(); i++)
        {
            hiddenElementsHeightSum += m_rowHeights[i];
        }
        float bottomContentPlaceHolderHeight = m_scrollRect.content.rect.height - hiddenElementsHeightSum;
        bottomContentPlaceHolderHeight -= m_verticalLayoutGroup.padding.top;
        bottomContentPlaceHolderHeight -= m_verticalLayoutGroup.padding.bottom;
        m_bottomContentPlaceholder.preferredHeight = bottomContentPlaceHolderHeight - m_verticalLayoutGroup.spacing;
        m_bottomContentPlaceholder.gameObject.SetActive(
            m_bottomContentPlaceholder.preferredHeight > 0);
    }

    private void HideRow(bool last)
    {
        //Debug.Log("Hiding row at scroll y " + m_scrollY.ToString("0.00"));

        int row = last ? m_visibleRowRange.Last() : m_visibleRowRange.from;
        ListViewCell removedCell = m_visibleCells[row];
        StoreCellForReuse(removedCell);
        m_visibleCells.Remove(row);
        m_visibleRowRange.count -= 1;
        if (!last)
        {
            m_visibleRowRange.from += 1;
        }
        this.onCellVisibilityChanged.Invoke(row, false);
    }

    private LayoutElement CreateEmptyContentPlaceHolderElement(string name)
    {
        GameObject go = new GameObject(name, typeof(RectTransform), typeof(LayoutElement));
        LayoutElement le = go.GetComponent<LayoutElement>();
        return le;
    }

    private int FindIndexOfRowAtY(float y)
    {
        //TODO : 二分搜索如果在干净的累积行高区域内，否则一直走到找到为止
        return FindIndexOfRowAtY(y, 0, m_cumulativeRowHeights.Length - 1);
    }

    private int FindIndexOfRowAtY(float y, int startIndex, int endIndex)
    {
        if (startIndex >= endIndex)
        {
            return startIndex;
        }
        int midIndex = (startIndex + endIndex) / 2;
        if (GetCumulativeRowHeight(midIndex) >= y)
        {
            return FindIndexOfRowAtY(y, startIndex, midIndex);
        }
        else
        {
            return FindIndexOfRowAtY(y, midIndex + 1, endIndex);
        }
    }

    private float GetCumulativeRowHeight(int row)
    {
        while (m_cleanCumulativeIndex < row)
        {
            m_cleanCumulativeIndex++;
            //Debug.Log("Cumulative index : " + m_cleanCumulativeIndex.ToString());
            m_cumulativeRowHeights[m_cleanCumulativeIndex] = m_rowHeights[m_cleanCumulativeIndex];
            if (m_cleanCumulativeIndex > 0)
            {
                m_cumulativeRowHeights[m_cleanCumulativeIndex] += m_cumulativeRowHeights[m_cleanCumulativeIndex - 1];
            }
        }
        return m_cumulativeRowHeights[row];
    }

    private void StoreCellForReuse(ListViewCell cell)
    {
        dataSource.OnCloseCell(cell);

        string reuseIdentifier = cell.reuseIdentifier;

        if (string.IsNullOrEmpty(reuseIdentifier))
        {
            GameObject.Destroy(cell.gameObject);
            return;
        }

        if (!m_reusableCells.ContainsKey(reuseIdentifier))
        {
            m_reusableCells.Add(reuseIdentifier, new LinkedList<ListViewCell>());
        }
        m_reusableCells[reuseIdentifier].AddLast(cell);
        cell.transform.SetParent(m_reusableCellContainer, false);
    }

    #endregion



}

internal static class RangeExtensions
{
    public static int Last(this Range range)
    {
        if (range.count == 0)
        {
            throw new System.InvalidOperationException("Empty range has no to()");
        }
        return (range.from + range.count - 1);
    }

    public static bool Contains(this Range range, int num)
    {
        return num >= range.from && num < (range.from + range.count);
    }
}
